home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1998 November: Tool Chest / Dev.CD Nov 98 TC.toast / Sample Code / Snippets / Toolbox / ProgressBars 1.0 / Documentation / ReadMe- ProgressBars next >
Encoding:
Text File  |  1996-09-17  |  17.9 KB  |  211 lines  |  [ttro/ttxt]

  1. ProgressBars
  2.  
  3.  
  4.  
  5. Written by:    Chris White,  Developer Technical Support
  6.  
  7. Copyright:    © 1996 by Apple Computer, Inc. All rights reserved.
  8.  
  9.  
  10.  
  11. What it does
  12.  
  13. "ProgressBars" is a bare bones application that demonstrates two implementations of the progress bars found in many Macintosh applications, namely the standard progress bar and the barber pole.  They use moveable modal dialogs, which can be awkward to implement because of the complication of handling events. It's not my intention to provide another variation on the graphics used, but to provide a solid implementation that developers can adapt to their own needs, and give an understanding of some of the issues involved. To that end, this application does very little other than fake a time consuming operation and display one of two progress bars. You can choose which type of progress bar and which implementation to use, giving a total of four variations.
  14.  
  15. It also demonstrates:
  16.     -The Thread Manager.
  17.     -Use of the new accessor routines which are provided as the first step to being Copland-savvy.
  18.  
  19.  
  20.  
  21. How to use the Application
  22.  
  23. It's fairly self explanatory, but to use the application just select either “Standard” or “Barber Pole” from the “ProgressBars” menu. To use the threaded implementation, select “Use Threads” from the same menu. A moveable modal dialog box contains the progress bar, and allows you to cancel the operation. After the operation is complete, the dialog is removed. The operation is a very simple routine which loops and calls Delay with each iteration.
  24.  
  25.  
  26.  
  27. Limitations and Bugs
  28.  
  29. This is not intended to be a definitive 'document' on how to implement a progress bar, but illustrates two approaches you can take. Like most projects, this was implemented under time constraints. There may be better methods available to you depending on your needs and the available time.
  30.  
  31. I'm not aware of any bugs, so if you find any then _please_ let me know.
  32.  
  33.  
  34.  
  35. Building
  36.  
  37. ProgressBars has been built under:
  38.     Metrowerks CodeWarrior 7 and 8
  39.     Symantec C++ 8.0.1
  40.     Symantec 7.0.4
  41.     MPW E.T.O. #19- 'Latest MPW': Symantec C++ for MPW and MrC.
  42.  
  43. The Symantec environments are using a slightly older version of the Universal Interfaces than MPW and CodeWarrior. Because the source code uses some of the new accessor routines which are provided as the first step to being Copland-savvy, you will need to use a later version of the Universal Interfaces than is provided with the Symantec products, or make some changes to the source code. To change the Universal Interfaces, simply place brackets around the existing folder and place the folder containing the later version into the same folder as the existing ones. The brackets will prevent the development environment from using the files contained within the old folder. Symantec C++ 8.0.1 does not contain the ThreadsLib library. You will need to obtain it from elsewhere, for example the MacOS SDK CD.
  44.  
  45.  
  46. The new accessor routines will allow you to remove direct accesses to some of the toolbox data structures, at a source code level. Currently, these accessors are implemented as macros. However, they will be available as true API entry points in Copland.
  47.  
  48. Two MPW make files are included: to build a 68K and a fat version using 'Latest MPW'. The make files have been set up to use Symantec C++ for MPW  (SC) and MrC, together with the interfaces and libraries from 'Latest MPW'.
  49.  
  50.  
  51.  
  52. Strategies
  53.  
  54.  
  55. Standard Implementation
  56.  
  57. The standard implementation is not ideal, although I suspect a similar approach is commonly used. It's based around a call back routine which carries out the operation, and periodically calls a routine to check for a small subset of events and to update the progress bar. This approach has some disadvantages, not least of which is the necessity for having another event loop. As you probably know, having more than one event loop within an application is something that should be avoided if at all possible. It isn't very clean, and can lead to some interesting complications. In addition, with this approach the application menus will not be handled correctly, unless you write specific code to disabled them while you process the operation.
  58.  
  59.  
  60. An Alternative?
  61.  
  62. An alternative approach, which hasn't been implemented here, is based around the idea of executing some of the operation each time the application receives a null event. The operation is broken down into a number of discrete stages. The operation is called each time a null event is received, and a single stage is executed before returning to the main event loop. The next time the application received a null event and the operation is called, execution progresses to the next stage by examining a counter maintained by the operation itself. This process continues along each stage until the operation is complete. The advantage of this approach is that the application's main event loop can be used to handle all the events. Unfortunately, in gaining this advantage the operation's themselves become harder to code, and the implementation becomes relatively tied to the application. As a result, it becomes difficult to implement it in a reusable manner.
  63.  
  64.  
  65. Threaded Implementation
  66.  
  67. The second implementation is an attempt to avoid these problems by taking advantage of the Thread Manager and using a multi threaded approach. In this implementation, the application executes its normal event loop, and the operation and dialog code are executed within their own threads. This makes the calling chain considerably simpler, with the complications being handled by the Thread Manager. Part of the event loop doesn't have to be duplicated to handle moving the dialog and checking for a click on the cancel button. We can still receive AppleEvents and do all the other things we do within our main event loop without any extra effort. We don't need special code to handle the menus correctly because that's taken care of by the normal application code which now gets executed simultaneously with the operation and dialog code. Simply put, the result is a clean implementation that can be reused easily.
  68.  
  69. In addition, this model is closer than you might think to a fully multi threaded application. This enables an application to carry out multiple operations simultaneously, with each operation displaying its own status dialog containing the progress bar. To achieve this, the code needs to be written in such a way as to be reentrant. Among other considerations, this means having to associate all the data with the operation itself and avoid the use of global and static data. Since the toolbox uses some global data, one must be careful to setup and restore this data just when it's needed. For example, as a general rule rather than setting up and restoring the Quickdraw port along with the life of a dialog, which is the familiar approach, the code should setup and restore the port before and after drawing. Of course, things are a little easier with cooperative threads since you know exactly when another thread may be executing. Occasionally, it just requires adjusting the position of the call to YieldToAnyThread. 
  70.  
  71. While I haven't gone as far as to implement a complete multi threaded demonstration, you can very easily hack the code to demonstrate just how close to one it actually is. This should be something that's tried, but not left that way. For more information, have a look at the section entitled “Hacking the demonstration” later in this document .
  72.  
  73.  
  74. Integer Data Types
  75.  
  76. To help keep the source code portable and avoid problems caused by compiler and architecture changes, the source code uses the integer typedefs defined by the Universal Interfaces in Types.h. For example:-
  77.  
  78. typedef unsigned short        UInt16;
  79. typedef signed short                SInt16;
  80. typedef unsigned long            UInt32;
  81. typedef signed long                    SInt32;
  82.  
  83. Although C purists may argue that for truly portable code the int data should be used extensively, experience has shown that using this data type throughout will cause more problems than not. As a general guideline, the int data type can be used when the size of the integer is only dependant on the rest of your own source code. The Apple defined typedefs can be used in all other cases. Specifically, when the size of the integer is dependant on source code outside of your control, for example when passing an address to a toolbox routine.
  84.  
  85.  
  86. Error Handling
  87.  
  88. There are two strategies that have been used to try and avoid the normal sort of problems that can occur as the result of an error exception. First, each routine cleans up after itself. Second, each routine leaves everything in the same state as it found it in.
  89.  
  90. Having each routine clean up after itself can lead to a number of difficulties in C as far as readability and maintainability are concerned. A block of code dedicated to cleaning up and returning from a routine can be a few lines in length. Duplicating this after checking each error code leads to inconsistencies, errors, and reduced readability. It's worth using C++ just for the exception handling features, but if you must use C you may want to use a similar approach to this. On occasion, we've used a goto statement to jump to a block of code at the end of the routine. If the clean up code should only be called as the result of an exception, the routine returns before dropping into this clean up code.
  91.  
  92. If the current port or resource file is changed, restore them before leaving the routine. Pay particular attention to this when an error occurs because this is usually the place where it's likely to be overlooked. If a handle needs to be in a particular state, save its current state using the HGetState routine before changing it. The HSetState routine can then be used to restore it, and the problems associated with a routine unintentional changing the state of a handle can be avoided.
  93.  
  94. Runtime errors that should never occur in a bug free program can use a DebugStr call. These can be conditionally compiled using a #define. For example, our ThreadTermination routine first validates the terminationProcParam parameter. This routine should always receive a valid value here, so we use the following code snippet to prevent the application from crashing, and we can also get an idea of what may have gone wrong. 
  95.  
  96.     #if DEBUGGING
  97.     if ( terminationProcParam == nil )
  98.         DebugStr ( "\p ThreadTermination: terminationProcParam is nil" );
  99.     #endif
  100.  
  101.  
  102.  
  103. Data Storage
  104.  
  105. The data structure of type tThreadedOperationRec is used internally to store the data associated with each threaded operation. It's allocated using the NewPtrClear routine and the pointer is then passed as the refCon to both the operation and dialog threads. Since we don't want to dispose of the record when one of the threads could still attempt to access the record, a termination routine is used by both threads to dispose of the record safely. The record contains a usageCount field that is decremented by the ThreadTermination routine. It does not dispose of the record until the value of usageCount reaches zero, ensuring the record is deallocated only when it's completely safe to do so. The refCon field is available for use by the operation routine, and drawnAmount is used to determine if the progress bar needs to be drawn again by comparing this value with that of doneAmount. The other fields are fairly self explanatory.
  106.  
  107. struct ThreadedOperationRec
  108. {
  109.     int                        usageCount;
  110.     void*                refCon;
  111.     Boolean        bCancelled;
  112.     Boolean        bBarberPole;
  113.     int                        maxAmount;
  114.     int                        doneAmount;
  115.     int                        drawnAmount;
  116.     Str255            theText;
  117. };
  118.  
  119. typedef struct ThreadedOperationRec tThreadedOperationRec, *tThreadedOperationPtr;
  120.  
  121.  
  122.  
  123. How the Interesting Stuff Works
  124.  
  125.  
  126. Thread Manager Support
  127.  
  128. The code that deals with the Thread Manager can be found in ThreadedProgress.c. Although Gestalt is used to check if the Thread Manager is available, a number of situations can occur that may cause the ThreadLib shared library to fail to load. Because we're weak linking to the library, we need to check we really have a connection to the shared library when building for the Code Fragment Manager. This can be done quite simply by checking any symbol against the constant kUnresolvedSymbolAddress. The following code snippet shows this, and is done after Gestalt has been used in the CheckConfiguration routine.
  129.  
  130. #if GENERATINGCFM
  131.     if ( gHasThreadManager )
  132.         gHasThreadManager = (NewThread != (void*) kUnresolvedSymbolAddress);
  133. #endif
  134.  
  135. So what can cause a shared library to fail to link? Well, the most common cause is a low memory situation. Even if the code is already loaded, a shared library may still allocate a new copy of its global data section. Although there are other possible causes, that's reason enough to make the extra check.
  136.  
  137.  
  138. Standard Implementation
  139.  
  140. The standard implementation is be used by calling the ProgressOperation routine. It's passed a routine pointer, a reference constant for use within the operation routine, and a string that's displayed in the progress bar dialog. The routine pointer parameter, of type tOperation, has the following prototype:-
  141.  
  142. Boolean StandardDemoOperation ( void* refCon, DialogRef theDialog );
  143.  
  144. This routine carries out the operation and periodically calls the UpdateProgressBar routine to update the progress bar and handle any events. The DialogRef parameter passed into the operation routine is passed on to the UpdateProgressBar routine, along with two integers that are used to calculate how much of the operation has been completed. If zeros are passed, the Barber Pole progress bar is used. It has the following prototype:-
  145.  
  146. Boolean UpdateProgressBar ( DialogRef theDialog, int doneAmount, int maxAmount );
  147.  
  148.  
  149.  
  150. Threaded Implementation
  151.  
  152. The threaded implementation is used by calling the ThreadedProgressOperation routine. It's passed a routine pointer, a reference constant for use within the operation routine, and a string that's displayed in the progress bar dialog,  a pointer to a 32-bit integer that will contain the error code upon completion, and a boolean which has a value of true if the barber pole progress bar should be used. The error code it returns only indicates if the threads were successfully started or not. The routine pointer parameter, of type tThreadedOperation, has the following prototype:-
  153.  
  154. pascal SInt32 ThreadedStandardDemoOperation ( tThreadedOperationPtr theInfo )
  155.  
  156. It calls the YieldToAnyThread routine periodically and returns its error code. The Thread Manager then deals with assigning this value to the error code pointer which was passed in to the ThreadedProgressOperation routine. It modifies the values of the doneAmount and maxAmount tThreadedOperationRec fields appropriately, assigning kBarberPoleFinished to the doneAmount field, if required, before exiting.
  157.  
  158. While this is going on, the dialog thread is calling YieldToAnyThread, and checking the same data which the operation thread is modifying. If necessary, it calculates a new rectangle for the progress bar and draws it. Finally, when the operation thread signals that it's finished, the dialog thread drops out of its loop and disposes of the dialog.
  159.  
  160. In this implementation, the dialog thread is polling the data it's sharing with the operation thread. This is rather an inconsiderate approach. Instead, a better approach would be to have the dialog thread block until the operation thread changes the data and has something for the dialog thread to do.
  161.  
  162.  
  163. Hacking the demonstration
  164.  
  165. As already mentioned, you can very easily hack the code to demonstrate just how close this implementation is to being fully multi threaded. This is something that can be tried, but should not be left like this. 
  166.  
  167. To allow more than one threaded operation to be kicked off at a time, comment out the following line of code in Menus.c As you can see below, it's in the EnableProgressBarsCmds routine and will stop the “ProgressBars” menu from being disabled while a moveable modal dialog box is being displayed.
  168.  
  169.  
  170. static Boolean EnableProgressBarsCmds ( WindowRef frontWindow )
  171. {
  172.     .
  173.     .
  174.  
  175. //    bHasDialog = frontWindow && GetWindowKind ( frontWindow ) == dialogKind;
  176.     .
  177.     .
  178.     
  179.  
  180. Because a static variable is used to keep track of which picture needs to be drawn for the next stage of the barber pole animation, two or more such progress bars will share this value and, as a result, skip some of their own animation frames. In short, this code is non-reentrant although the problems which occur by treating it as such are only cosmetic. Note that if you went on to enable the “File” menu, it would become possible to quit the application without it disposing of a number of dialogs. This is because quitting would terminate any dialog threads without giving them a chance to terminate naturally. It's also possible to create similar situations as the result of exception handling. The recommended solution is to dispose of the dialog within the thread termination routine.
  181.  
  182. Lastly, please be aware that the resulting application breaks the user interface guidelines by allowing access to the menus when displaying moveable modal dialogs.
  183.  
  184.  
  185.  
  186. Additional functionality to add
  187.  
  188. -The standard progress bar could be drawn using Color Quickdraw.
  189. -The progress bars could be drawn in 3-D.
  190. -Use a Control Definition for the drawing, perhaps optionally?
  191. -Better menu support could be added to the standard implementation.
  192. -More events could be handled in the standard implementation.
  193. -Although less complicated , in the interests of completeness I'd like to implement a modal dialog version.
  194. -Again, in the interests of completeness I'd like to implement the alternative approach discussed here.
  195. -I'd also like to implement a complete multi threaded application, using document windows and operations that do something useful. Maybe next time, or perhaps as a separate sample.
  196.  
  197.  
  198.  
  199. Bugs, Comments and Suggestions
  200.  
  201. Bug reports, comments and suggestions should be made to the AppleLink address DEVSUPPORT and marked for my attention. As always, the level of continued support for this project will largely depend on your feedback. It's my hope to be able to add additional features to this application, but without your feedback the features that are important to you may not make it into a future release.
  202.  
  203. Enjoy!
  204.  
  205. Chris White
  206. Developer Technical Support
  207. January '96
  208.  
  209. © 1996 by Apple Computer, Inc. All rights reserved.
  210.  
  211.